import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.TransactionProxyFactoryBean;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import tx.config.RootConfig;
import tx.service.UserService;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Properties;

@ContextConfiguration(classes = {RootConfig.class})
public class T05_SpringTransaction {

    private static final String SCHEMA_SQL = "classpath:db/schema-mysql.sql";

    @Autowired
    private UserService userService;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private ApplicationContext applicationContext;

    @BeforeEach
    public void init() throws SQLException {
        Resource resource = applicationContext.getResource(SCHEMA_SQL);
        ScriptUtils.executeSqlScript(dataSource.getConnection(), resource);
    }

    @Test
    public void tx() {
        // AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RootConfig.class);
        // UserService userService = applicationContext.getBean(UserService.class);
        userService.insertUser();
        // applicationContext.close();
    }

    // ==============================================================================================================

    /**
     * 单独配置每个 Bean 的代理
     */
    // @Bean
    // @Primary
    public TransactionProxyFactoryBean transactionProxyFactoryBean(PlatformTransactionManager transactionManager,
                                                                   UserService userService) {
        TransactionProxyFactoryBean transactionProxyFactoryBean = new TransactionProxyFactoryBean();
        // 设置事务管理器，去管理这个 Bean 的事务关系
        transactionProxyFactoryBean.setTransactionManager(transactionManager);
        // 设置目标对象
        // 这里需要注意：这里设置了目标对象，你的 HelloServiceImpl 就不允许再交给 Spring 管理了，否则会有两个 Bean，不做处理注入会报错
        transactionProxyFactoryBean.setTarget(userService);
        // 设置事务相关的属性：transactionAttributes
        Properties transactionAttributes = new Properties();
        transactionAttributes.setProperty("*", "PROPAGATION_REQUIRED");
        transactionProxyFactoryBean.setTransactionAttributes(transactionAttributes);

        return transactionProxyFactoryBean;
    }

    // ==============================================================================================================

    /**
     * 配置一个事务模版，用于编程式事务，编程式事务使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager
     * 这里面也可以通过构造函数传入一个 TransactionDefinition 接口的实例。表示事务的一些属性
     */
    // @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager,
                                                   JdbcTemplate jdbcTemplate) {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        transactionTemplate.execute(transactionStatus -> { // 使用
            // 向数据库插入一条记录
            String sql = "INSERT INTO `employee`(`username`,password) VALUES(?,?)";
            jdbcTemplate.update(sql, "username", "password");
            // 做其余的事情，可能抛出异常
            System.out.println(1 / 0);
            return "service hello";
        });
        return transactionTemplate;
    }

    /**
     * 编程式事务 TransactionTemplate，还可以用 JDBC 原生的方式，利用 Connection 或者 SqlSession
     */
    public TransactionManager transactionManager(PlatformTransactionManager transactionManager,
                                                 JdbcTemplate jdbcTemplate) {
        // 构造一个准备使用此事务的定义信息
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        transactionDefinition.setReadOnly(false);
        // 隔离级别，-1 表示使用数据库默认级别
        transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 根据此事务属性，拿到一个事务实例。注意此处的入参是一个：TransactionDefinition
        TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
        try {
            // =================业务逻辑 start，必须 try，但无需 finally=======================
            // 向数据库插入一条记录
            String sql = "insert into user (name,age) values ('jhxxb',23)";
            jdbcTemplate.update(sql);
            // 做其余的事情，可能抛出异常
            System.out.println(1 / 0);
            // =================业务逻辑 end=======================
            // 提交事务
            transactionManager.commit(transaction);
        } catch (Exception e) {
            // 若发现异常，事务进行回滚
            transactionManager.rollback(transaction);
            throw e;
        }

        return transactionManager;
    }
}
